﻿#include "testcaseform.h"
#include "ui_testcaseform.h"
#include "global_funcs.h"
#include "errcode.h"
#include "inputstringsdialog.h"
#include <QStandardItemModel>
#include <QDebug>
#include <QMessageBox>
#include <QTimer>
#include <QAction>
#include <QFileDialog>
#include <QCloseEvent>
#include <QPainter>
#include <QInputDialog>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>
#include <QComboBox>
#include <QCheckBox>
#include <QButtonGroup>
#ifdef WIN32
#include <QAxObject>
#else
#include <xlslib/xlslib.h>
using namespace xlslib_core;
#endif

#define METHOD_NAME_COL 0
#define METHOD_RESULT_COL 1
#define METHOD_TIME_COL 2

#define PARAM_NAME_COL 0
#define PARAM_TYPE_COL 1
#define PARAM_INPUT_VALUE_COL 2
#define PARAM_EXPECTED_VALUE_COL 3
#define PARAM_RETURN_VALUE_COL 4
#define PARAM_COMPARE_RESULT_COL 5

#define TEST_CASE_FILES_PATH "./TestCase"
#ifdef WIN32
#define TEST_CASE_FILES "Test case files (*.tst)"
#else
#define TEST_CASE_FILES "Test case files (*)"
#endif

TestCaseForm::TestCaseForm(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TestCaseForm),
    isPaused(false),
    testObj(nullptr)
{
    ui->setupUi(this);
    setAttribute(Qt::WA_DeleteOnClose);
    QButtonGroup* rdGroup = new QButtonGroup(this);
    rdGroup->setExclusive(true);
    rdGroup->addButton(ui->rdContinue);
    rdGroup->addButton(ui->rdStop);

    connect(this, &TestCaseForm::moduleNameChanged, this, &TestCaseForm::on_moduleNameChanged);
    connect(this, &TestCaseForm::aMethodTestBegin, this, &TestCaseForm::on_aMethodTestBegin, Qt::BlockingQueuedConnection);
    connect(this, &TestCaseForm::aMethodTestFinished, this, &TestCaseForm::on_aMethodTestFinished, Qt::BlockingQueuedConnection);
    connect(this, &TestCaseForm::setCurrentMethod, this, &TestCaseForm::on_setCurrentMethod, Qt::BlockingQueuedConnection);
    connect(this, &TestCaseForm::testPause, this, &TestCaseForm::on_testPause, Qt::BlockingQueuedConnection);
    connect(&watcher, SIGNAL(finished()), this, SLOT(on_testFinished()));

    action = new QAction(this);
    action->setCheckable(true);
    connect(action, SIGNAL(triggered()), this, SLOT(show()));
    connect(action, SIGNAL(triggered()), this, SLOT(setFocus()));

    isUntitled = true;
    setWindowTitle("[*]");

    modelMethods = new QStandardItemModel(this);
    QStringList labels;
    labels.append(QStringLiteral("名称"));
    labels.append(QStringLiteral("结果"));
    labels.append(QStringLiteral("用时"));
    modelMethods->setHorizontalHeaderLabels(labels);
    ui->tvMethods->setModel(modelMethods);
    connect(ui->tvMethods->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, &TestCaseForm::on_tvMethods_currentRowChanged);
    ui->tvMethods->setItemDelegate(new MethodsTableDelegate());

    modelParams = new QStandardItemModel(this);
    labels.clear();
    labels.append(QStringLiteral("名称"));
    labels.append(QStringLiteral("类型"));
    labels.append(QStringLiteral("输入值"));
    labels.append(QStringLiteral("期望值"));
    labels.append(QStringLiteral("返回值"));
    labels.append(QStringLiteral("比对结果"));
    modelParams->setHorizontalHeaderLabels(labels);
    ui->tvParams->setModel(modelParams);
    ui->tvParams->setItemDelegate(new ParamsTableDelegate(this));
}

TestCaseForm::~TestCaseForm()
{
    delete ui;
    if (testObj)
        delete testObj;
}

QSize TestCaseForm::sizeHint() const
{
    return QSize(800, 600);
}

bool TestCaseForm::newFile(const QString& moduleName, const QStringList& methodSignatures)
{
    static int documentNumber = 1;

    curFile = QString(QStringLiteral("新测试用例%1")).arg(documentNumber);
    setWindowTitle(curFile + "[*]");
    action->setText(curFile);
    isUntitled = true;
    setWindowModified(true);
    ++documentNumber;

    this->moduleName = moduleName;
    emit moduleNameChanged();

    foreach (const QString& methodSignature, methodSignatures) {
        QString methodName = methodSignature.left(methodSignature.indexOf('('));
        QStandardItem* methodItem = new QStandardItem(methodName);
        methodItem->setData(methodSignature);
        modelMethods->appendRow(methodItem);
    }

    if (!createTestObj(moduleName, &testObj)) {
        qCritical() << "createTestObj failed!";
        return false;
    }

    return true;
}

bool TestCaseForm::save()
{
    if (isUntitled) {
        return saveAs();
    }
    else {
        return saveFile(curFile);
    }
}

bool TestCaseForm::saveAs()
{
    QString fileName =
            QFileDialog::getSaveFileName(this, QStringLiteral("另存为"), TEST_CASE_FILES_PATH, TEST_CASE_FILES);
    if (fileName.isEmpty())
        return false;

    return saveFile(fileName);
}

TestCaseForm *TestCaseForm::open(QWidget *parent)
{
    QString fileName =
            QFileDialog::getOpenFileName(parent, QStringLiteral("打开文件"), TEST_CASE_FILES_PATH, TEST_CASE_FILES);
    if (fileName.isEmpty())
        return nullptr;

    return openFile(fileName, parent);
}

TestCaseForm *TestCaseForm::openFile(const QString &fileName, QWidget *parent)
{
    TestCaseForm* form = new TestCaseForm(parent);
    if (form->readFile(fileName)) {
        form->setCurrentFile(fileName);
        return form;
    }
    else {
        delete form;
        return nullptr;
    }
}

void TestCaseForm::on_moduleNameChanged()
{
    ui->gbMethods->setTitle(QString(QStringLiteral("【%1】方法列表")).arg(moduleName));
}

void TestCaseForm::on_tvMethods_currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
{
    disconnect(modelParams, SIGNAL(itemChanged(QStandardItem *)), this, SLOT(on_paramTable_itemChanged(QStandardItem *)));

    if (current.isValid()) {
        QString currentMethodSignature = modelMethods->item(current.row())->data().toString();
        QList<QByteArray> parameterNames;
        if (!enumParam(moduleName, currentMethodSignature, parameterNames)) {
            qCritical() << "enumParam failed!";
            return;
        }

        const TMethodParams& params = methods[current.row()];
        modelParams->removeRows(0, modelParams->rowCount());
        for (int i = 0; i < parameterNames.count(); i++) {
            QString paramName = QString::fromLatin1(parameterNames[i]);
            QList<QStandardItem *> items;
            items.append(new QStandardItem(paramName));

            QStandardItem* paramTypeItem = new QStandardItem();
            int type = params[paramName].type;
            paramTypeItem->setData(params[paramName].type);
            items.append(paramTypeItem);

            QStandardItem* inputValueItem = new QStandardItem(params[paramName].inputValue);
            QStandardItem* expectedValueItem = new QStandardItem(params[paramName].expectedValue);
            if (type == QVariant::Bool) {
                setItemCheckable(inputValueItem);
                setItemCheckable(expectedValueItem);
            }
            items.append(inputValueItem);
            items.append(expectedValueItem);

            items.append(new QStandardItem(params[paramName].returnedValue));
            items.append(new QStandardItem(params[paramName].compResult));
            modelParams->appendRow(items);
        }
    }
    else
        modelParams->removeRows(0, modelParams->rowCount());

    connect(modelParams, SIGNAL(itemChanged(QStandardItem *)), this, SLOT(on_paramTable_itemChanged(QStandardItem *)));

    ui->gbParamList->setTitle(QString(QStringLiteral("[%1]参数列表")).arg(modelMethods->item(current.row())->text()));
}

void TestCaseForm::on_pbAddMethod_clicked()
{
    QStringList methodSignatures;
    if (!enumMethod(moduleName, methodSignatures)) {
        qCritical() << "enumMethod failed!";
        QMessageBox::critical(this, windowTitle(), QStringLiteral(""));
        return;
    }

    bool ok;
    QString item = QInputDialog::getItem(this, QStringLiteral("新增方法"),
                                       QStringLiteral("方法列表："), methodSignatures, 0, false, &ok);
    if (ok && !item.isEmpty()) {
        QStandardItem* methodItem = new QStandardItem(item.left(item.indexOf('(')));
        methodItem->setData(item);

        int row = 0;
        if (ui->tvMethods->currentIndex().isValid())
            row = ui->tvMethods->currentIndex().row() + 1;

        modelMethods->insertRow(row, methodItem);
        ui->tvMethods->setCurrentIndex(modelMethods->index(row, 0));
        ui->tvMethods->setFocus();
        setWindowModified(true);
    }
}

void TestCaseForm::on_pbDelMethod_clicked()
{
    if (ui->tvMethods->currentIndex().isValid()) {
        if (QMessageBox::question(this, QStringLiteral("删除方法"), QStringLiteral("确定要删除选定的方法吗？")) == QMessageBox::Yes) {
            int row = ui->tvMethods->currentIndex().row();
            modelMethods->removeRow(row);
            for (int i = row; i < methods.count() - 1; i++)
                methods[i] = methods[i + 1];
            methods.remove(methods.count() - 1);
            setWindowModified(true);
        }
    }
}

void TestCaseForm::on_pbMoveUpMethod_clicked()
{
    if (ui->tvMethods->currentIndex().isValid()) {
        int row = ui->tvMethods->currentIndex().row();
        if (row > 0) {
            disconnect(ui->tvMethods->selectionModel(), &QItemSelectionModel::currentRowChanged,
                    this, &TestCaseForm::on_tvMethods_currentRowChanged);
            QList<QStandardItem*> items = modelMethods->takeRow(row);
            modelMethods->insertRow(row - 1, items);
            TMethodParams destParamsCopy = methods[row - 1];
            methods[row - 1] = methods[row];
            methods[row] = destParamsCopy;
            ui->tvMethods->setCurrentIndex(modelMethods->index(row - 1, 0));
            connect(ui->tvMethods->selectionModel(), &QItemSelectionModel::currentRowChanged,
                    this, &TestCaseForm::on_tvMethods_currentRowChanged);
            setWindowModified(true);
        }
    }
}

void TestCaseForm::on_pbMoveDownMethod_clicked()
{
    if (ui->tvMethods->currentIndex().isValid()) {
        int row = ui->tvMethods->currentIndex().row();
        if (row < modelMethods->rowCount() - 1) {
            disconnect(ui->tvMethods->selectionModel(), &QItemSelectionModel::currentRowChanged,
                    this, &TestCaseForm::on_tvMethods_currentRowChanged);
            QList<QStandardItem*> items = modelMethods->takeRow(row);
            modelMethods->insertRow(row + 1, items);
            TMethodParams destParamsCopy = methods[row + 1];
            methods[row + 1] = methods[row];
            methods[row] = destParamsCopy;
            ui->tvMethods->setCurrentIndex(modelMethods->index(row + 1, 0));
            connect(ui->tvMethods->selectionModel(), &QItemSelectionModel::currentRowChanged,
                    this, &TestCaseForm::on_tvMethods_currentRowChanged);
            setWindowModified(true);
        }
    }
}

void TestCaseForm::on_paramTable_itemChanged(QStandardItem *item)
{
    if (ui->tvMethods->currentIndex().isValid()) {
        if (item->isCheckable()) {
            switch (item->checkState()) {
            case Qt::Checked:
                item->setText("true");
                break;
            case Qt::Unchecked:
                item->setText("false");
                break;
            case Qt::PartiallyChecked:
                item->setText("");
                break;
            }
        }

        QString paramName = item->model()->data(item->model()->index(item->row(), PARAM_NAME_COL)).toString();
        TParamValue &params = methods[ui->tvMethods->currentIndex().row()][paramName];
        switch (item->column()) {
        case PARAM_INPUT_VALUE_COL:
            params.inputValue = item->text();
            setWindowModified(true);
            break;
        case PARAM_EXPECTED_VALUE_COL:
            params.expectedValue = item->text();
            setWindowModified(true);
            break;
        case PARAM_TYPE_COL:
            params.type = item->data().toInt();
            setWindowModified(true);
            break;
        case PARAM_RETURN_VALUE_COL:
            params.returnedValue = item->text();
            break;
        case PARAM_COMPARE_RESULT_COL:
            params.compResult = item->text();
            break;
        }
    }
}

void TestCaseForm::on_paramTypeCmb_currentIndexChanged(int index)
{
    int currentRow = ui->tvParams->currentIndex().row();
    if (qobject_cast<QComboBox*>(sender())->currentData().toInt() == QVariant::Bool) {
        QStandardItem* inputValueItem = new QStandardItem();
        setItemCheckable(inputValueItem);
        modelParams->setItem(currentRow, PARAM_INPUT_VALUE_COL, inputValueItem);
        QStandardItem* expectedValueItem = new QStandardItem();
        setItemCheckable(expectedValueItem);
        modelParams->setItem(currentRow, PARAM_EXPECTED_VALUE_COL, expectedValueItem);
    }
    else {
        modelParams->setItem(currentRow, PARAM_INPUT_VALUE_COL, new QStandardItem());
        modelParams->setItem(currentRow, PARAM_EXPECTED_VALUE_COL, new QStandardItem());
    }
}

void TestCaseForm::on_pbStart_clicked()
{
    QPushButton* thisBtn = qobject_cast<QPushButton*>(sender());
    if (thisBtn->text() == QStringLiteral("开始")) {
        enableControls(false);

        int startRow = 0;
        if (ui->tvMethods->currentIndex().isValid())
            startRow = ui->tvMethods->currentIndex().row();

        QFuture<void> future = QtConcurrent::run([=] {
            for (int k = 0; k < ui->sbCircleTime->value() && !watcher.isCanceled(); k++) {
                for (int i = startRow; i < modelMethods->rowCount() && !watcher.isCanceled(); i++) {
                    emit setCurrentMethod(i);

                    bool isContinue;
                    emit aMethodTestBegin(isContinue);
                    if (!isContinue)
                        break;

                    QString methodName = modelMethods->item(i, METHOD_NAME_COL)->text();
                    if (!testOneMethod(testObj, methodName) && ui->rdStop->isChecked())
                        break;

                    if (isPaused) {
                        emit testPause(isPaused);

                        while (isPaused)
                            QThread::msleep(100);

                        emit testPause(isPaused);
                    }
                }
            }
        });
        watcher.setFuture(future);
    }
    else if (thisBtn->text() == QStringLiteral("暂停")) {
        isPaused = true;
    }
    else if (thisBtn->text() == QStringLiteral("继续")) {
        isPaused = false;
    }
}

void TestCaseForm::on_pbStop_clicked()
{
    watcher.cancel();
}

void TestCaseForm::on_pbStep_clicked()
{
    if (!ui->tvMethods->currentIndex().isValid())
        return;

    enableControls(false);

    QFuture<void> future = QtConcurrent::run([=] {
        for (int i = 0; i < ui->sbCircleTime->value() && !watcher.isCanceled(); i++) {
            bool isContinue;
            emit aMethodTestBegin(isContinue);
            if (!isContinue)
                return;

            QString methodName = modelMethods->item(ui->tvMethods->currentIndex().row(), METHOD_NAME_COL)->text();
            if (!testOneMethod(testObj, methodName) && ui->rdStop->isChecked())
                break;

            if (isPaused) {
                emit testPause(isPaused);

                while (isPaused)
                    QThread::msleep(100);

                emit testPause(isPaused);
            }
        }
    });
    watcher.setFuture(future);
}

void TestCaseForm::on_aMethodTestBegin(bool& isContinue)
{
    isContinue = true;

    for (int i = 0; i < modelParams->rowCount(); i++) {
        int type = modelParams->item(i, PARAM_TYPE_COL)->data().toInt();
        if (type == 0) {
            ui->tvParams->setCurrentIndex(modelParams->index(i, PARAM_TYPE_COL));
            QString name = modelParams->item(i, PARAM_NAME_COL)->text();
            QMessageBox::critical(this, windowTitle(), QString(QStringLiteral("请选择参数[%1]的类型！")).arg(name));
            isContinue = false;
            break;
        }
    }
}

void TestCaseForm::on_aMethodTestFinished(const QVariantList &paramsRetVal, int& result, int timeUsed)
{
    if (result != ERR_INVOKE) {
        for (int i = 0; i < modelParams->rowCount(); i++) {
            bool isMismatch = false;
            int type = modelParams->item(i, PARAM_TYPE_COL)->data().toInt();
            if (type == QVariant::ByteArray) {
                QString text = paramsRetVal[i].toByteArray().toHex(' ');
                modelParams->setItem(i, PARAM_RETURN_VALUE_COL, new QStandardItem(text));
            }
            else
                modelParams->setItem(i, PARAM_RETURN_VALUE_COL, new QStandardItem(paramsRetVal[i].toString()));

            QVariant expectedValue = modelParams->data(modelParams->index(i, PARAM_EXPECTED_VALUE_COL));
            if (expectedValue.toString().isEmpty()) {
                modelParams->setItem(i, PARAM_COMPARE_RESULT_COL, new QStandardItem());
                continue;
            }

            if (type == QVariant::ByteArray)
                expectedValue = QByteArray::fromHex(expectedValue.toString().toLatin1());

            if (type == QVariant::String) {
                QStringList valuesToCompare = expectedValue.toString().split(';');
                foreach (const QString& aValue, valuesToCompare) {
                    if (!paramsRetVal[i].toString().contains(aValue)) {
                        result = ERR_MISMATCH;
                        isMismatch = true;
                        break;
                    }
                }
            }
            else {
                if (paramsRetVal[i] != expectedValue) {
                    result = ERR_MISMATCH;
                    isMismatch = true;
                }
            }

            if (isMismatch)
                modelParams->setItem(i, PARAM_COMPARE_RESULT_COL, new QStandardItem(QStringLiteral("不一致")));
            else
                modelParams->setItem(i, PARAM_COMPARE_RESULT_COL, new QStandardItem(QStringLiteral("一致")));
        }
    }

    ui->tvParams->resizeRowsToContents();

    int currentIndex = ui->tvMethods->currentIndex().row();
    QString info;
    switch (result) {
    case ERR_OK:
        info = QStringLiteral("成功");
        break;
    case ERR_PARAM:
        info = QStringLiteral("参数错误");
        break;
    case ERR_INVOKE:
        info = QStringLiteral("调用失败");
        break;
    case ERR_MISMATCH:
        info = QStringLiteral("比对错误");
        break;
    case ERR_TIMEOUT:
        info = QStringLiteral("调用超时");
        break;
    default:
        info = QStringLiteral("未知错误");
        break;
    }
    modelMethods->setItem(currentIndex, METHOD_RESULT_COL, new QStandardItem(info));
    modelMethods->setItem(currentIndex, METHOD_TIME_COL, new QStandardItem(QString("%1ms").arg(timeUsed)));
    ui->tvMethods->selectRow(currentIndex);
}

void TestCaseForm::on_setCurrentMethod(int index)
{
    ui->tvMethods->selectRow(index);
    ui->tvMethods->scrollTo(ui->tvMethods->currentIndex());
}

void TestCaseForm::on_testFinished()
{
    enableControls(true);
}

void TestCaseForm::on_testPause(bool isPaused)
{
    ui->pbStart->setText(isPaused ? QStringLiteral("继续") : QStringLiteral("暂停"));
}

void TestCaseForm::on_pbClear_clicked()
{
    if (QMessageBox::question(this, windowTitle(), QStringLiteral("确定要清除测试结果吗？")) == QMessageBox::Yes) {
        TMethods::Iterator iterMethods = methods.begin();
        while (iterMethods != methods.end()) {
             TMethodParams& methodParams = iterMethods.value();
             TMethodParams::Iterator iterParams = methodParams.begin();
             while (iterParams != methodParams.end()) {
                 iterParams.value().returnedValue.clear();
                 iterParams.value().compResult.clear();
                 iterParams++;
             }
             iterMethods++;
        }

        for (int i = 0; i < modelMethods->rowCount(); i++) {
            modelMethods->setData(modelMethods->index(i, 1), QString());
            modelMethods->setData(modelMethods->index(i, 2), QString());
        }

        on_tvMethods_currentRowChanged(ui->tvMethods->currentIndex(), QModelIndex());
    }
}

void TestCaseForm::on_pbExport_clicked()
{
#ifdef WIN32
    QString fileFullPath = QFileDialog::getSaveFileName(this, windowTitle(), qApp->applicationDirPath(),
        QStringLiteral("Excel文件(*.xlsx)"));
    if (!fileFullPath.isEmpty()) {
        if (!fileFullPath.endsWith(".xlsx")) {
            fileFullPath += ".xlsx";
        }

        QAxObject* excel = new QAxObject(this);
        if (!excel->setControl("Excel.Application")) {
            delete excel;
            QMessageBox::critical(this, windowTitle(), QStringLiteral("此计算机还未安装EXCEL软件！"));
            return;
        }

        excel->dynamicCall("SetVisible(bool)", false);
        excel->setProperty("DisplayAlerts", false);
        QAxObject* workbooks = excel->querySubObject("Workbooks");
        workbooks->dynamicCall("Add");
        QAxObject* workbook = excel->querySubObject("ActiveWorkbook");
        QAxObject* worksheets = workbook->querySubObject("Sheets");
        QAxObject* worksheet = worksheets->querySubObject("Item(int)", 1);
        worksheet->setProperty("Name", curFile);
        int columnCount = 0;

        /* 方法列表的标题 */
        for (int i = 0; i < modelMethods->columnCount(); i++) {
            char columnID = 'A' + columnCount++;
            QString range(columnID);
            QString caption = modelMethods->headerData(i, Qt::Horizontal).toString();
            QAxObject* cell = worksheet->querySubObject("Range(const QString&)", range + "1");
            cell->dynamicCall("SetValue(const QVariant&)", caption);
        }

        int rowID = 2;

        for (int i = 0; i < modelMethods->rowCount(); i++) {
            ui->tvMethods->selectRow(i);

            /* 方法列表的内容 */
            columnCount = 0;
            for (int j = 0; j < modelMethods->columnCount(); j++) {
                char columnID = 'A' + columnCount++;
                QString range = QString("%1%2").arg(columnID).arg(rowID);
                QAxObject* cell = worksheet->querySubObject("Range(const QString&)", range);
                QVariant data = modelMethods->data(modelMethods->index(i, j)).toString();
                cell->dynamicCall("SetValue(const QVariant&)", data);
            }

            rowID++;

            QAxObject* cell = worksheet->querySubObject("Range(const QString&)", QString("A%2").arg(rowID));
            cell->dynamicCall("SetValue(const QVariant&)", "参数列表");

            /* 参数列表的标题 */
            columnCount = 0;
            for (int j = 0; j < modelParams->columnCount(); j++) {
                char columnID = 'B' + columnCount++;
                QString range = QString("%1%2").arg(columnID).arg(rowID);
                QAxObject* cell = worksheet->querySubObject("Range(const QString&)", range);
                QVariant data = modelParams->headerData(j, Qt::Horizontal).toString();
                cell->dynamicCall("SetValue(const QVariant&)", data);
            }

            rowID++;

            /* 参数列表的内容 */
            for (int k = 0; k < modelParams->rowCount(); k++) {
                columnCount = 0;
                for (int j = 0; j < modelParams->columnCount(); j++) {
                    char columnID = 'B' + columnCount++;
                    QString range = QString("%1%2").arg(columnID).arg(rowID);
                    QAxObject* cell = worksheet->querySubObject("Range(const QString&)", range);
                    QVariant data = modelParams->data(modelParams->index(k, j)).toString();
                    cell->dynamicCall("SetValue(const QVariant&)", data);
                }

                rowID++;
            }

            rowID++;
        }

        QVariant ret = workbook->dynamicCall("SaveAs(const QString&)", QDir::toNativeSeparators(fileFullPath));
        if (ret.isValid() && ret == true) {
            QMessageBox::information(this, windowTitle(), QStringLiteral("生成成功！"));
        }
        else {
            QMessageBox::critical(this, windowTitle(), QStringLiteral("生成失败！"));
        }
        workbook->dynamicCall("Close(bool)", false);
        excel->dynamicCall("Quit(void)");
        excel->clear();
        delete excel;
    }
#else
    QString fileFullPath = QFileDialog::getSaveFileName(this, windowTitle(), qApp->applicationDirPath(),
        QStringLiteral("Excel文件(*.xlsx)"));
    if (!fileFullPath.isEmpty()) {
        if (!fileFullPath.endsWith(".xlsx")) {
            fileFullPath += ".xlsx";
        }

        workbook wb;
        worksheet* ws;
        xf_t* xf = wb.xformat();

        //命名Excel
        ws = wb.sheet(curFile.toStdString());
        int columnCount = 0;

        /* 方法列表的标题 */
        for (int i = 0; i < modelMethods->columnCount(); i++) {
            QString caption = modelMethods->headerData(i, Qt::Horizontal).toString();
            ws->label(0,columnCount++,caption.toStdString(), xf);
        }

        int rowID = 1;
        for (int i = 0; i < modelMethods->rowCount(); i++) {
            ui->tvMethods->selectRow(i);
            /* 方法列表的内容 */
            columnCount = 0;
            for (int j = 0; j < modelMethods->columnCount(); j++) {
                QString data = modelMethods->data(modelMethods->index(i, j)).toString();
                ws->label(rowID,columnCount++,data.toStdString(), xf);
            }

            rowID++;
            ws->label(rowID,0,"参数列表", xf);
            /* 参数列表的标题 */
            columnCount = 1;
            for (int j = 0; j < modelParams->columnCount(); j++) {
                QString data = modelParams->headerData(j, Qt::Horizontal).toString();
                ws->label(rowID,columnCount++,data.toStdString(), xf);
            }

            rowID++;
            /* 参数列表的内容 */
            for (int k = 0; k < modelParams->rowCount(); k++) {
                columnCount = 1;
                for (int j = 0; j < modelParams->columnCount(); j++) {
                    QString data = modelParams->data(modelParams->index(k, j)).toString();
                    ws->label(rowID,columnCount++,data.toStdString(), xf);
                }

                rowID++;
            }
            rowID++;
        }

        //保存至fileFullPath
        if (wb.Dump(fileFullPath.toStdString()) == NO_ERRORS) {
            QMessageBox::information(this, windowTitle(), QStringLiteral("生成成功！"));
        }
        else {
            QMessageBox::critical(this, windowTitle(), QStringLiteral("生成失败！"));
        }
    }
#endif
}

void TestCaseForm::closeEvent(QCloseEvent *event)
{
    if (watcher.isRunning()) {
        event->ignore();
        return;
    }

    if (okToContinue()) {
        event->accept();
    }
    else {
        event->ignore();
    }
}

bool TestCaseForm::okToContinue()
{
    if (this->isWindowModified()) {
        int r = QMessageBox::warning(this, qApp->applicationName(),
                                     QStringLiteral("文件 %1 已经被修改.\n"
                                        "你想要保存修改吗？").arg(strippedName(curFile)),
                                     QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
        if (r == QMessageBox::Yes) {
            return save();
        } else if (r == QMessageBox::Cancel) {
            return false;
        }
    }
    return true;
}

bool TestCaseForm::saveFile(const QString &fileName)
{
    if (writeFile(fileName)) {
        setCurrentFile(fileName);
        return true;
    } else {
        return false;
    }
}

void TestCaseForm::setCurrentFile(const QString &fileName)
{
    curFile = fileName;
    isUntitled = false;
    action->setText(strippedName(curFile));
    setWindowTitle(strippedName(curFile) + "[*]");
    setWindowModified(false);
}

bool TestCaseForm::readFile(const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly)) {
        qCritical() << "File open failed!";
        return false;
    }

    try {
        QDataStream inStream(&file);
        inStream >> *this;
    } catch (std::exception& e) {
        qCritical() << "Read file failed, " << e.what();
        return false;
    }

    if (!createTestObj(moduleName, &testObj)) {
        qCritical() << "createTestObj failed!";
        return false;
    }

    return true;
}

bool TestCaseForm::writeFile(const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly)) {
        qCritical() << "File open failed!";
        return false;
    }

    QDataStream outStream(&file);
    outStream << *this;
    return true;
}

QString TestCaseForm::strippedName(const QString &fullFileName)
{
    return QFileInfo(fullFileName).fileName();
}

void TestCaseForm::enableControls(bool enabled)
{
    ui->gbMethods->setEnabled(enabled);
    ui->gbParamList->setEnabled(enabled);
    ui->pbStart->setText(enabled == true ? QStringLiteral("开始") : QStringLiteral("暂停"));
    ui->pbStop->setEnabled(!enabled);
    ui->pbStep->setEnabled(enabled);
    ui->pbExport->setEnabled(enabled);
    ui->widgetCircleTime->setEnabled(enabled);
    ui->widgetErrDealType->setEnabled(enabled);
}

void TestCaseForm::setItemCheckable(QStandardItem *item)
{
    item->setEditable(false);
    item->setCheckable(true);
    item->setUserTristate(true);
    QString text = item->text();
    if (text.isEmpty())
        item->setCheckState(Qt::PartiallyChecked);
    else
        item->setCheckState(item->text() == "true" ? Qt::Checked : Qt::Unchecked);
}

bool TestCaseForm::testOneMethod(QObject* testObj, const QString &methodName)
{
    QVariantList params;
    int retVal;

    for (int i = 0; i < modelParams->rowCount(); i++) {
        QString inputValue = modelParams->item(i, PARAM_INPUT_VALUE_COL)->text();
        int type = modelParams->item(i, PARAM_TYPE_COL)->data().toInt();
        if (type == QVariant::ByteArray)
            params.append(QByteArray::fromHex(inputValue.toLatin1()));
        else
            params.append(inputValue);
    }

    QTime time;
    time.start();
    if (!invokeMethod(testObj, methodName, params, retVal))
        retVal = ERR_INVOKE;

    emit aMethodTestFinished(params, retVal, time.elapsed());

    return retVal == ERR_OK;
}

QDataStream &operator>>(QDataStream &in, TestCaseForm &form)
{
    quint32 magic;
    qint32 version;
    in >> magic;
    in >> version;
    if (magic != 0xA1B1C1D1) {
        qCritical() << "Bad file format!";
        throw std::invalid_argument("Bad file format!");
    }

    if (version == 1) {
        in.setVersion(QDataStream::Qt_5_6);
        in >> form.moduleName;
        emit form.moduleNameChanged();
        int rowCount = 0;
        in >> rowCount;
        for (int i = 0; i < rowCount; i++) {
            QString name;
            in >> name;
            QString signature;
            in >> signature;
            QStandardItem* item = new QStandardItem(name);
            item->setData(signature);
            form.modelMethods->appendRow(item);
        }

        in >> form.methods;
    }
    else {
        qCritical() << "Bad file version!";
        throw std::invalid_argument("Bad file version!");
    }

    return in;
}

QDataStream &operator<<(QDataStream &out, TestCaseForm &form)
{
    quint32 magic = 0xA1B1C1D1;
    qint32 version = 1;
    out << magic;
    out << version;
    out.setVersion(QDataStream::Qt_5_6);
    out << form.moduleName;
    out << form.modelMethods->rowCount();
    for (int i = 0; i < form.modelMethods->rowCount(); i++) {
        QStandardItem* item = form.modelMethods->item(i);
        out << item->text();
        out << item->data().toString();
    }
    out << form.methods;

    return out;
}

void TestCaseForm::ParamsTableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.column() != PARAM_INPUT_VALUE_COL && index.column() != PARAM_EXPECTED_VALUE_COL
            && index.column() != PARAM_TYPE_COL)
        painter->fillRect(option.rect, QApplication::palette().color(QPalette::Window));

    if (index.column() == PARAM_COMPARE_RESULT_COL) {
        if (index.data().toString().compare(QStringLiteral("一致")) == 0)
            painter->fillRect(option.rect, Qt::green);
        else if (index.data().toString().compare(QStringLiteral("不一致")) == 0)
            painter->fillRect(option.rect, Qt::red);
    }
    else if (index.column() == PARAM_TYPE_COL) {
        QStyleOptionViewItem myOption(option);
        initStyleOption(&myOption, index);
        switch(index.data(Qt::UserRole + 1).toInt()) {
        case QVariant::Int:
            myOption.text = "Int";
            break;
        case QVariant::String:
            myOption.text = "String";
            break;
        case QVariant::ByteArray:
            myOption.text = "ByteArray";
            break;
        case QVariant::Bool:
            myOption.text = "Bool";
            break;
        }

        QStyledItemDelegate::paint(painter, myOption, index);
        return;
    }

    QStyledItemDelegate::paint(painter, option, index);
}

QWidget *TestCaseForm::ParamsTableDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.column() != PARAM_INPUT_VALUE_COL && index.column() != PARAM_EXPECTED_VALUE_COL && index.column() != PARAM_TYPE_COL)
        return nullptr;

    if (index.column() == PARAM_TYPE_COL) {
        QComboBox* paramTypeComb = new QComboBox(parent);
        paramTypeComb->addItem("Int", (int)QVariant::Int);
        paramTypeComb->addItem("String", (int)QVariant::String);
        paramTypeComb->addItem("Bool", (int)QVariant::Bool);
        paramTypeComb->addItem("ByteArray", (int)QVariant::ByteArray);
        paramTypeComb->setCurrentIndex(paramTypeComb->findData(index.data(Qt::UserRole + 1).toInt()));
        QObject::connect(paramTypeComb, SIGNAL(currentIndexChanged(int)), parentForm, SLOT(on_paramTypeCmb_currentIndexChanged(int)));
        return paramTypeComb;
    }
    else {
        int type = index.model()->data(index.model()->index(index.row(), PARAM_TYPE_COL), Qt::UserRole + 1).toInt();
        switch (type) {
        case QVariant::Int:
        {
            QLineEdit* inputValueEditor = qobject_cast<QLineEdit*>(QStyledItemDelegate::createEditor(parent, option, index));
            QRegExp regExp("[0-9]*");
            inputValueEditor->setValidator(new QRegExpValidator(regExp));
            return inputValueEditor;
        }
        case QVariant::String:
        {
            if (index.column() == PARAM_EXPECTED_VALUE_COL)
                return new InputStringsDialog(parentForm);
            else {
                QLineEdit* inputValueEditor = qobject_cast<QLineEdit*>(QStyledItemDelegate::createEditor(parent, option, index));
                return inputValueEditor;
            }
        }
        case QVariant::ByteArray:
        {
            QLineEdit* inputValueEditor = qobject_cast<QLineEdit*>(QStyledItemDelegate::createEditor(parent, option, index));
            QRegExp regExp("[a-fA-F0-9 ]*");
            inputValueEditor->setValidator(new QRegExpValidator(regExp));
            return inputValueEditor;
        }
        default:
            return nullptr;
        }
    }
}

void TestCaseForm::ParamsTableDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    if (index.column() == PARAM_TYPE_COL) {
        QComboBox* paramTypeComb = qobject_cast<QComboBox*>(editor);
        model->setData(index, paramTypeComb->currentData().toInt(), Qt::UserRole + 1);
    }
    else if (index.column() == PARAM_EXPECTED_VALUE_COL
             && model->data(model->index(index.row(), PARAM_TYPE_COL), Qt::UserRole + 1).toInt() == QVariant::String) {
        InputStringsDialog* dlg = qobject_cast<InputStringsDialog*>(editor);
        model->setData(index, dlg->getResult());
    }
    else
        QStyledItemDelegate::setModelData(editor, model, index);
}

void TestCaseForm::ParamsTableDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if (index.column() == PARAM_EXPECTED_VALUE_COL
            && index.model()->data(index.model()->index(index.row(), PARAM_TYPE_COL), Qt::UserRole + 1).toInt() == QVariant::String) {
        InputStringsDialog* dlg = qobject_cast<InputStringsDialog*>(editor);
        dlg->setInputStrings(index.data().toString());
    }
    else
        QStyledItemDelegate::setEditorData(editor, index);
}

void TestCaseForm::ParamsTableDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.column() == PARAM_EXPECTED_VALUE_COL
            && index.model()->data(index.model()->index(index.row(), PARAM_TYPE_COL), Qt::UserRole + 1).toInt() == QVariant::String) {
        InputStringsDialog* dlg = qobject_cast<InputStringsDialog*>(editor);
        QPoint oriPos = option.rect.topLeft();
        oriPos.ry() += option.rect.height();
        dlg->move(parentForm->ui->tvParams->mapToGlobal(oriPos));
    }
    else
        QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}

void TestCaseForm::MethodsTableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    painter->fillRect(option.rect, QApplication::palette().color(QPalette::Window));
    if (index.column() == METHOD_RESULT_COL && !index.data().toString().isEmpty()) {
        if (index.data().toString().compare(QStringLiteral("成功")) == 0)
            painter->fillRect(option.rect, Qt::green);
        else
            painter->fillRect(option.rect, Qt::red);
    }
    QStyledItemDelegate::paint(painter, option, index);
}

QDataStream &operator<<(QDataStream &out, const TParamValue &paramValue)
{
    out << paramValue.type;
    out << paramValue.inputValue;
    out << paramValue.expectedValue;

    return out;
}

QDataStream &operator>>(QDataStream &in, TParamValue &paramValue)
{
    in >> paramValue.type;
    in >> paramValue.inputValue;
    in >> paramValue.expectedValue;

    return in;
}
